package com.easy.secret.codec;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

import com.easy.secret.enums.RSAMode;

/**
 * @ProjectName: esay-secret-server
 * @Package: com.easy.secret.codec
 * @ClassName: RSACodec
 * @Description: RSA非对称加密
 * @Author: wangjc
 * @CreateDate: 2023/11/17
 * @UpdateUser: wangjc
 * @UpdateDate: 2023/11/17
 * @UpdateRemark: 更新内容
 * @Version: 1.0
 */
public class RSACodec {
	private String mode = "RSA";
	private int keySize = 2048;
	private static String SIGNATURE_ALGORITHM = "MD5withRSA";
	private static RSACodec codec;

	public RSACodec() {
	}

	public static RSACodec instance() {
		return instance("RSA", null);
	}

	public static RSACodec instance(String mode) {
		return instance(mode, null);
	}

	public static RSACodec instance(Integer keySize) {
		return instance((String) null, keySize);
	}

	public static RSACodec instance(String mode, Integer keySize) {
		if (codec == null) {
			codec = new RSACodec();
		}

		if (StringUtils.isBlank(mode)) {
			mode = "RSA";
		}

		if (keySize == null) {
			keySize = 2048;
		}

		codec.setMode(mode);
		codec.setKeySize(keySize);
		return codec;
	}

	public static RSACodec instanceBySignType(String signType) throws NoSuchAlgorithmException {
		if (codec == null) {
			codec = instance();
		}

		RSACodec var10000 = codec;
		SIGNATURE_ALGORITHM = signType;
		return codec;
	}

	public Map generateKey() throws NoSuchAlgorithmException {
		return this.generateKey(this.keySize);
	}

	public Map generateKey(Integer keySize) throws NoSuchAlgorithmException {
		KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
		SecureRandom secrand = new SecureRandom();
		secrand.setSeed("initSeed".getBytes());
		if (keySize != null) {
			this.checkKeySize(keySize);
		}

		keygen.initialize(keySize == null ? this.keySize : keySize, secrand);
		KeyPair keys = keygen.genKeyPair();
		Map<String, Object> map = new HashMap();
		map.put("publicKey", Base64.encodeBase64String(keys.getPublic().getEncoded()));
		map.put("privateKey", Base64.encodeBase64String(keys.getPrivate().getEncoded()));
		return map;
	}

	public String encryptByPubKey(String data, String publicKey)
			throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException,
			InvalidKeyException, InvalidKeySpecException, IOException {
		return this.encryptByPubKey(data, publicKey, "UTF-8");
	}

	public String encryptByPubKey(String data, String publicKey, String charset)
			throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException,
			InvalidKeyException, InvalidKeySpecException, IOException {
		byte[] encrypt = this.encryptByPubKey(data.getBytes(charset), Base64.decodeBase64(publicKey));
		return Base64.encodeBase64String(encrypt);
	}

	public byte[] encryptByPubKey(byte[] data, byte[] pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException,
			NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, IOException {
		PublicKey publicKey = this.getPublicKey(pubKey);
		Cipher cipher = Cipher.getInstance(this.mode);
		cipher.init(1, publicKey);
		int inputLen = data.length;
		int offLen = 0;
		int i = 0;
		ByteArrayOutputStream bops = null;

		byte[] cache;
		try {
			for (bops = new ByteArrayOutputStream(); inputLen - offLen > 0; offLen = 117 * i) {
				if (inputLen - offLen > 117) {
					cache = cipher.doFinal(data, offLen, 117);
				} else {
					cache = cipher.doFinal(data, offLen, inputLen - offLen);
				}

				bops.write(cache);
				++i;
			}

			bops.close();
			cache = bops.toByteArray();
		} finally {
			if (bops != null) {
				bops.close();
			}
		}
		return cache;
	}

	public String encryptByPriKey(String data, String privateKey)
			throws InvalidKeySpecException, BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException,
			NoSuchPaddingException, InvalidKeyException, IOException {
		return this.encryptByPriKey(data, privateKey, "UTF-8");
	}

	public String encryptByPriKey(String data, String privateKey, String charset)
			throws InvalidKeySpecException, BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException,
			NoSuchPaddingException, InvalidKeyException, IOException {
		byte[] encrypt = this.encryptByPriKey(data.getBytes(charset), Base64.decodeBase64(privateKey));
		return Base64.encodeBase64String(encrypt);
	}

	public byte[] encryptByPriKey(byte[] data, byte[] priKey) throws NoSuchAlgorithmException, InvalidKeySpecException,
			NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException, IOException {
		if (!RSAMode.OAEP.getName().equals(this.mode) && !RSAMode.OAEP_SHA1.getName().equals(this.mode)
				&& !RSAMode.OAEP_SHA256.getName().equals(this.mode)) {
			PrivateKey privateKey = this.getPrivateKey(priKey);
			Cipher cipher = Cipher.getInstance(this.mode);
			cipher.init(1, privateKey);
			int inputLen = data.length;
			int offLen = 0;
			int i = 0;
			ByteArrayOutputStream bops = null;

			try {
				byte[] cache;
				for (bops = new ByteArrayOutputStream(); inputLen - offLen > 0; offLen = 117 * i) {
					if (inputLen - offLen > 117) {
						cache = cipher.doFinal(data, offLen, 117);
					} else {
						cache = cipher.doFinal(data, offLen, inputLen - offLen);
					}
					bops.write(cache);
					++i;
				}

				bops.close();
				cache = bops.toByteArray();
				return cache;
			} finally {
				if (bops != null) {
					bops.close();
				}
			}
		} else {
			throw new RuntimeException(this.mode + " is not support");
		}
	}

	public String decryptByPubKey(String data, String publicKey)
			throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException,
			InvalidKeyException, InvalidKeySpecException, IOException {
		return this.decryptByPubKey(data, publicKey, (Integer) null, "UTF-8");
	}

	public String decryptByPubKey(String data, String publicKey, Integer keySize)
			throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException,
			InvalidKeyException, InvalidKeySpecException, IOException {
		return this.decryptByPubKey(data, publicKey, keySize, "UTF-8");
	}

	public byte[] decryptByPubKey(byte[] data, byte[] pubKey, Integer keySize)
			throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
			BadPaddingException, IllegalBlockSizeException, IOException {
		PublicKey publicKey = this.getPublicKey(pubKey);
		Cipher cipher = Cipher.getInstance(this.mode);
		cipher.init(2, publicKey);
		if (keySize == null) {
			keySize = this.keySize;
		}

		int size = keySize / 8;
		ByteArrayOutputStream byteArrayOutputStream = null;

		byte[] cache;
		try {
			int inputLen = data.length;
			int offLen = 0;
			int i = 0;

			for (byteArrayOutputStream = new ByteArrayOutputStream(); inputLen - offLen > 0; offLen = size * i) {
				if (inputLen - offLen > size) {
					cache = cipher.doFinal(data, offLen, size);
				} else {
					cache = cipher.doFinal(data, offLen, inputLen - offLen);
				}

				byteArrayOutputStream.write(cache);
				++i;
			}

			byteArrayOutputStream.close();
			cache = byteArrayOutputStream.toByteArray();
		} finally {
			if (byteArrayOutputStream != null) {
				byteArrayOutputStream.close();
			}
		}

		return cache;
	}

	public String decryptByPubKey(String data, String publicKey, Integer keySize, String charset)
			throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException,
			InvalidKeyException, InvalidKeySpecException, IOException {
		byte[] pubKey = Base64.decodeBase64(publicKey);
		byte[] design = this.decryptByPubKey(Base64.decodeBase64(data), pubKey, keySize);
		return this.byteToStr(design, charset);
	}

	public byte[] decryptByPriKey(byte[] data, byte[] priKey, int keySize)
			throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
			BadPaddingException, IllegalBlockSizeException, IOException {
		PrivateKey privateKey = this.getPrivateKey(priKey);
		Cipher cipher = Cipher.getInstance(this.mode);
		cipher.init(2, privateKey);
		int size = keySize / 8;
		ByteArrayOutputStream byteArrayOutputStream = null;

		byte[] cache;
		try {
			int inputLen = data.length;
			int offLen = 0;
			int i = 0;

			for (byteArrayOutputStream = new ByteArrayOutputStream(); inputLen - offLen > 0; offLen = size * i) {
				if (inputLen - offLen > size) {
					cache = cipher.doFinal(data, offLen, size);
				} else {
					cache = cipher.doFinal(data, offLen, inputLen - offLen);
				}

				byteArrayOutputStream.write(cache);
				++i;
			}

			byteArrayOutputStream.close();
			cache = byteArrayOutputStream.toByteArray();
		} finally {
			if (byteArrayOutputStream != null) {
				byteArrayOutputStream.close();
			}
		}

		return cache;
	}

	public String decryptByPriKey(String data, String privateKey, Integer keySize)
			throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException,
			InvalidKeyException, InvalidKeySpecException, IOException {
		return this.decryptByPriKey(data, privateKey, keySize, "UTF-8");
	}

	public String decryptByPriKey(String data, String privateKey, Integer keySize, String charset)
			throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException,
			InvalidKeyException, InvalidKeySpecException, IOException {
		if (keySize != null) {
			this.checkKeySize(keySize);
		}

		byte[] priKey = Base64.decodeBase64(privateKey);
		byte[] design = this.decryptByPriKey(Base64.decodeBase64(data), priKey, keySize);
		return this.byteToStr(design, charset);
	}

	public String byteToStr(byte[] buffer, String charset) {
		try {
			byte[] data = new byte[buffer.length];
			int index = 0;

			for (int i = 0; i < buffer.length; ++i) {
				if (buffer[i] != 0) {
					data[index] = buffer[i];
					++index;
				}
			}

			return new String(data, 0, index, charset);
		} catch (Exception var6) {
			return "";
		}
	}

	public String sign(String data, String priKey)
			throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
		return Base64.encodeBase64String(this.sign(data.getBytes(), Base64.decodeBase64(priKey)));
	}

	public byte[] sign(byte[] data, byte[] priKey)
			throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
		PrivateKey privateKey = this.getPrivateKey(priKey);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(privateKey);
		signature.update(data);
		return signature.sign();
	}

	public boolean verify(String data, String sign, String pubKey)
			throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
		return this.verify(data.getBytes(), Base64.decodeBase64(sign), Base64.decodeBase64(pubKey));
	}

	public boolean verify(byte[] data, byte[] sign, byte[] pubKey)
			throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
		PublicKey publicKey = this.getPublicKey(pubKey);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(publicKey);
		signature.update(data);
		return signature.verify(sign);
	}

	private void checkKeySize(Integer keySize) {
		if (keySize != 1024 && keySize != 2048 && keySize != 4096) {
			throw new RuntimeException("keySize 不合法");
		}
	}

	private PublicKey getPublicKey(byte[] pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		return keyFactory.generatePublic(new X509EncodedKeySpec(pubKey));
	}

	private PrivateKey getPrivateKey(byte[] priKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(priKey));
	}

	public String getMode() {
		return this.mode;
	}

	public void setMode(String mode) {
		this.mode = mode;
	}

	public int getKeySize() {
		return this.keySize;
	}

	public void setKeySize(int keySize) {
		this.keySize = keySize;
	}
}
